Exception handling


Exception handling is one of the most important programing tools features. If the
tool that we are using to develop applications does not provide any mean of handling
errors that occurs at run time, then we can not rely on this tool to build robust
applications.

There are two types of errors:
Compile time error, such as "Type mismatch", "unknown
identifier", etc. This type of errors prevent the compiler to resume the compilation
procedure and producing the executable file, example:

var
Name: string;
begin
 Name:= 12; // Type mismatch
end;

The second type of errors is Run-time error. In this type, the program compiled
and run succussfully, but unexpected user input or any unexpected event may crash
the application execution such as "divistion by zero". Example:

var
i, x: Integer;
begin
i:= StrToInt(Edit1.Text);
 X:= 5 div i;
 Label1.Caption:= IntToStr(X);
end;

The previous example compiles succesfully, but when the user enter
0 in the edit
box, you will get "
division by zero error" and the rest code of procedure will not
execute.


Another example is
I/O erros. Some times we tries to open files that does not exist,
or the disk that we write into is full, or we attempt to read from file but end of
file is reached, or any other I/O error.

In all the previous cases, Delphi has the same
default exception handling to treat
all run-time erros. If we forget to handle any exception, Delphi will use it's default
handling. The default exception handing is:

1. Display the proper error message,
as the attached picture.

2. Exit from the current procedure or function, for example if Division by zero
occured at the previous example the next line will not be executed :
Label1.Caption:=
IntToStr(X);

This handling is not enough in most of programming cases, for example, suppose that
we have a memo, and a text file. We want to read from this file and append it's contents
to the memo. Befor this operation we hide the memo for fast reading, and we display
it again after finishing. The code is:

var
F: TextFile;
Line: string;
begin
Memo1.Visible:= False;
Memo1.Clear;
AssignFile(F, 'Data.txt');
Reset(F);
while not Eof(F) do
begin
  Readln(F, Line);
  Memo1.Lines.Add(Line);
end;
 CloseFile(F);
Memo1.Visible:= True;
end;

Suppose that the file 'Data.txt' does not exist or an error occurs while reading
from file. Delphi will rais I/O error. Because we did not add our own exception handling,
Delphi will handle the exception for us, and it will rais the error "
File not found"
or "
Error while reading file" and will exit the  procedure after the statement Reset(F); if
the file does not exists
or after Readln(F, Line);
statement if an error occurs while reading, also the rest of procedure statements
will not be executed. The most important statements on the rest of code are:

CloseFile(F);
Memo1.Visible:= True;

The result will be: leaving the file in use at the case of second error and the
memo will be invisible. This will be a strange behaviour of our application.


try .. finally block:

Try Finally block is ued to ensure that the finally block will execute. In finally
block you can free resources, close tables and files, etc. The statements that exist
in finally block will be executed in normal cases and in error cases. The structure
of try finally is:

try
Statement(s);
finally
 Statement(s);
end;

Example:

var
F: TextFile;
Line: string;
begin
 try
   Memo1.Visible:= False;
  Memo1.Clear;
  AssignFile(F, 'Data.txt');
  Reset(F);
  while not Eof(F) do
  begin
    Readln(F, Line);
    Memo1.Lines.Add(Line);
  end;
  CloseFile(F);
 finally
   Memo1.Visible:= True;
 end;
end;

The application will ensure the execution of
Memo1.Visible:= True statement in all
conditions.

Another example:

try
 Table1.Open;
Label1.Caption:= Table1.FieldByName('Name').AsString;    
// Any other Table1 manipulations
finally
 Table1.Close;
end;
 


Try .. Except block:

Try Except block is used when you need to execute certain statements if a run-time
error is occured. The structure of try except is:

try
 Statement1(s);
except
 Statement2(s);
end;

If an error occure in
Statement1(s), the execution will jump to except block (Statement2(s)).
Example:

var
i, x: Integer;
begin
try
i:= StrToInt(Edit1.Text);
X:= 5 div i;
Label1.Caption:= IntToStr(X);
except
    ShowMessage('Invalid data entry');
  Edit1.Clear;
 end; // try
Edit2.Text:= Edit1.Text;
end;

In this case instead of showing the default error message, you can show your own
message and execute another statements, such as clearing the text box that contains
the wrong entry such as 0 or a letter.
Except block statements will be executed only
if an error occured. In normal cases the execution will not reach this block.

Another important thing is that when an error occures and you handle it with except
block, the execution will resume normally after try except block or try finally block.
In the previous example
Edit2.Text:= Edit1.Text; will be executed.


on E: Exception:

When a run-time error occures, an instance of
Exception class will be raised, this
instance contains information about the error such as error message. Example:

var
i, x: Integer;
begin
try
i:= StrToInt(Edit1.Text);
X:= 5 div i;
Label1.Caption:= IntToStr(X);
except
 on E: Exception do
  begin
   ShowMessage('Invalid data entry: ' +
E.Message);
  Edit1.Clear;
 end; // on E:

 end; // try
Edit2.Text:= Edit1.Text;
end;

Exception class is a generic exception class, and you can be more specific on handling
errors, for example
EInOutError is a descendent of Exception class, and it can be
used only with I/O Error. Example:

var
F: TextFile;
Line: string;
begin
try
  Memo1.Visible:= False;
  Memo1.Clear;
  AssignFile(F, 'Data.txt');
  Reset(F);
  while not Eof(F) do
  begin
    Readln(F, Line);
    Memo1.Lines.Add(Line);
  end;
  CloseFile(F);
except
 on E: EInOutError do
   begin
    ShowMessage(E.Message + #10 +
     'Error code: ' + IntToStr(
E.ErrorCode));
  end; // on E:
end; // try
Memo1.Visible:= True;
end;

Note that
ErrorCode property does not exist in Exception class.


Nested Try:

Try .. finally and Try except blocks can be nested. If an error occures in certain
position of our code, the execution will jump to the nearest or the current
try .. finally,
or
try .. except block. Example:

try
A;
B;
try
  C;
except
  D;
end;
E;
except
F;
end;

If an error occures in A, B or E, the execution will jump to F. If an error occures
in C, the execution will jump to D, then it will resume to E:


Notes:

-If you want to test try .. except or try .. finally blocks, it is recommended that
you run your application separately of Delphi such as command prompt or from
Start/Run,
because if you run it from the debugger (Dephi IDE), the debugger will show it's
message and will pause program execution, but you can press F9 to execute your handling
block and resume your application.

- In the applications that run alone without user interference, services, web appliations,
or any other application that does not require a user to deal with it directly using
it's interface, you should not display any message, instead you can write this error
message and time of error in a log file or send it via E-Mail, because if you display
this message, no one will know that there is an error occures and no one can will
click "Ok". Moreover the large number of error message boxes will overload the memory
and resources. Also you must not rely on default exception handling, and you should
add
try .. finally, try .. except blocks whenever you expect run-time erros of these
types of applications.